﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Collections;
using System.ComponentModel;

namespace Xceed.Silverlight.DragDrop
{
  public sealed class DragDropContext : DependencyObject, INotifyPropertyChanged
  {
    public DragDropContext( FrameworkElement draggedElement, DraggedElementConfiguration configuration, IEnumerable<FrameworkElement> siblingDraggedElements )
    {
      if( draggedElement == null )
        throw new ArgumentNullException( "draggedElement" );

      if( configuration == null )
        throw new ArgumentNullException( "configuration" );

      this.DraggedElement = draggedElement;
      m_siblingCollection = new SiblingsCollection( siblingDraggedElements );
      m_siblingCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( OnSiblingsCollectionChanged );

      // Setting the configuration will set the CanDropCursor, CanDropCursortemplate,
      // NoDropCursor, and NoDropCursorTemplate properties.
      this.Configuration = configuration;
    }

    public DragDropContext( FrameworkElement draggedElement,
                            DraggedElementConfiguration configuration )
      : this( draggedElement, configuration, null )
    {
      //Nothing to do, calling another ctor with appropriate initialization values.
    }

    public DragDropContext( FrameworkElement draggedElement,
                            FrameworkElement draggedElementRoot,
                            IEnumerable<FrameworkElement> siblingDraggedElements,
                            DraggedElementConfiguration configuration,
                            object data )
      : this( draggedElement, configuration, siblingDraggedElements )
    {
      this.DraggedElementRoot = draggedElementRoot;
      this.Data = data;
    }

    #region CurrentDropTarget Property

    public IDropTarget CurrentDropTarget
    {
      get
      {
        if( DragDropManager.IsDragActive && ( DragDropManager.CurrentDragDropContext == this ) )
        {
          return DragDropManager.DraggedOverReference;
        }

        return null;
      }
    }

    #endregion

    #region LastMouseMoveEventArgs Property

    public MouseEventArgs LastMouseMoveEventArgs
    {
      get;
      internal set;
    }

    #endregion

    #region MouseOffset Property

    public Point MouseOffset
    {
      get;
      internal set;
    }

    #endregion

    #region Configuration Property

    public DraggedElementConfiguration Configuration
    {
      get
      {
        return m_configuration;
      }
      internal set
      {
        if( m_configuration != value )
        {
          m_configuration = value;

          this.CanDropCursor = m_configuration.CanDropCursor;
          this.NoDropCursor = m_configuration.NoDropCursor;
          this.NoDropCursorTemplate = m_configuration.NoDropCursorTemplate;
          this.CanDropCursorTemplate = m_configuration.CanDropCursorTemplate;
        }
      }
    }

    #endregion

    #region ParentDragContext Attached Property

    public static readonly DependencyProperty ParentDragContextProperty = DependencyProperty.RegisterAttached(
      "ParentDragContext",
      typeof( DragDropContext ),
      typeof( DragDropContext ),
      new PropertyMetadata( ( DragDropContext )null ) );

    public static DragDropContext GetParentDragContext( DependencyObject obj )
    {
      return ( DragDropContext )obj.GetValue( DragDropContext.ParentDragContextProperty );
    }

    internal static void SetParentDragContext( DependencyObject obj, DragDropContext value )
    {
      obj.SetValue( DragDropContext.ParentDragContextProperty, value );
    }

    #endregion

    #region CanDropCursor Property

    public DragDropCursor CanDropCursor
    {
      get;
      set;
    }

    #endregion

    #region CanDropCursorTemplate Property

    public DataTemplate CanDropCursorTemplate
    {
      get;
      set;
    }

    #endregion

    #region NoDropCursor Property

    public DragDropCursor NoDropCursor
    {
      get;
      set;
    }

    #endregion

    #region NoDropCursorTemplate Property

    public DataTemplate NoDropCursorTemplate
    {
      get;
      set;
    }

    #endregion

    #region DraggedElement Property

    public FrameworkElement DraggedElement
    {
      get;
      internal set;
    }

    #endregion

    #region DraggedElementRoot Property

    public FrameworkElement DraggedElementRoot
    {
      get;
      internal set;
    }

    #endregion

    #region SiblingDraggedElements Property

    public SiblingsCollection SiblingDraggedElements
    {
      get
      {
        return m_siblingCollection;
      }
    }

    private void OnSiblingsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.OnPropertyChanged( "SiblingsCount" );
    }

    private SiblingsCollection m_siblingCollection;

    #endregion

    #region SiblingsCount Property

    public int SiblingsCount
    {
      get
      {
        return m_siblingCollection.Count;
      }
    }

    #endregion

    #region Data Property

    public object Data
    {
      get
      {
        return m_data;
      }
      set
      {
        this.VerifyFrozen();

        if( value != m_data )
        {
          m_data = value;
        }
      }
    }

    private object m_data;

    #endregion

    #region IsFrozen Property

    internal bool IsFrozen
    {
      get;
      private set;
    }

    #endregion

    #region DragStarting Event

    public event EventHandler<DragDropCancelEventArgs> DragStarting;

    internal void OnDragStarting( DragDropCancelEventArgs e )
    {
      if( this.DragStarting != null )
      {
        this.DragStarting( this, e );
      }
    }

    #endregion

    #region DragStarted Event

    public event EventHandler DragStarted;

    internal void OnDragStarted()
    {
      if( this.DragStarted != null )
      {
        this.DragStarted( this, EventArgs.Empty );
      }
    }

    #endregion

    #region DragMove Event

    public event EventHandler<DragDropMoveEventArgs> DragMove;

    internal void OnDragMove( DragDropMoveEventArgs e )
    {
      if( this.DragMove != null )
      {
        this.DragMove( this, e );
      }
    }

    #endregion

    #region DragCompleted Event

    public event EventHandler<DragDropEventArgs> DragCompleted;

    internal void OnDragCompleted( DragDropEventArgs e )
    {
      if( this.DragCompleted != null )
      {
        this.DragCompleted( this, e );
      }
    }

    #endregion

    #region DragAborted Event

    public event EventHandler<DragDropEventArgs> DragAborted;

    internal void OnDragAborted( DragDropEventArgs e )
    {
      if( this.DragAborted != null )
      {
        this.DragAborted( this, e );
      }
    }

    #endregion

    #region DragPaused Event

    public EventHandler<DragDropEventArgs> DragPaused;

    internal void OnDragPaused( DragDropEventArgs e )
    {
      if( this.DragPaused != null )
      {
        this.DragPaused( this, e );
      }
    }

    #endregion

    #region DragResumed Event

    public event EventHandler<DragDropEventArgs> DragResumed;

    internal void OnDragResumed( DragDropEventArgs e )
    {
      if( this.DragResumed != null )
      {
        this.DragResumed( this, e );
      }
    }

    #endregion

    #region Internal Methods

    internal DragDropCursor GetCursor( bool canDrop )
    {
      if( canDrop )
      {
        return this.CanDropCursor;
      }
      else
      {
        return this.NoDropCursor;
      }
    }

    internal DataTemplate GetCustomCursorTemplate( bool canDrop )
    {
      if( canDrop )
      {
        return this.CanDropCursorTemplate;
      }
      else
      {
        return this.NoDropCursorTemplate;
      }
    }

    internal void FreezeDragContext()
    {
      this.Freeze();
      m_siblingCollection.Freeze();
    }

    private void Freeze()
    {
      this.IsFrozen = true;
    }

    private void VerifyFrozen()
    {
      if( this.IsFrozen )
        throw new InvalidOperationException( "An attempt was made to modify the Data property of the DragDropContext after the drag-drop operation has started." );
    }

    #endregion

    #region Private Fields

    private DraggedElementConfiguration m_configuration;

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged( string propertyName )
    {
      if( this.PropertyChanged != null )
      {
        this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

    #endregion

    #region Sub-Classes

    public class SiblingsCollection : IList<FrameworkElement>, INotifyCollectionChanged
    {
      internal SiblingsCollection( IEnumerable<FrameworkElement> startingElements )
      {
        m_innerCollection = new ObservableCollection<FrameworkElement>();

        if( startingElements != null )
        {
          using( IEnumerator<FrameworkElement> enumerator = startingElements.GetEnumerator() )
          {
            while( enumerator.MoveNext() )
            {
              m_innerCollection.Add( enumerator.Current );
            }
          }
        }

        m_innerCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( OnInnerCollectionChanged );
      }

      #region IsFrozen Property

      internal bool IsFrozen
      {
        get;
        private set;
      }

      #endregion

      internal void Freeze()
      {
        this.IsFrozen = true;
      }

      private void VerifyFrozen()
      {
        if( this.IsFrozen )
          throw new InvalidOperationException( "An attempt was made to modify the SiblingDraggedElements collection after the drag-drop operation has started." );
      }

      private void OnInnerCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
      {
        if( this.CollectionChanged != null )
        {
          this.CollectionChanged( this, e );
        }
      }

      #region IList<FrameworkElement> Members

      public int IndexOf( FrameworkElement item )
      {
        return m_innerCollection.IndexOf( item );
      }

      public void Insert( int index, FrameworkElement item )
      {
        this.VerifyFrozen();

        m_innerCollection.Insert( index, item );
      }

      public void RemoveAt( int index )
      {
        this.VerifyFrozen();

        m_innerCollection.RemoveAt( index );
      }

      public FrameworkElement this[ int index ]
      {
        get
        {
          return m_innerCollection[ index ];
        }
        set
        {
          this.VerifyFrozen();

          m_innerCollection[ index ] = value;
        }
      }

      #endregion

      #region ICollection<FrameworkElement> Members

      public void Add( FrameworkElement item )
      {
        this.VerifyFrozen();

        m_innerCollection.Add( item );
      }

      public void Clear()
      {
        this.VerifyFrozen();

        m_innerCollection.Clear();
      }

      public bool Contains( FrameworkElement item )
      {
        return m_innerCollection.Contains( item );
      }

      public void CopyTo( FrameworkElement[] array, int arrayIndex )
      {
        m_innerCollection.CopyTo( array, arrayIndex );
      }

      public int Count
      {
        get
        {
          return m_innerCollection.Count;
        }
      }

      public bool IsReadOnly
      {
        get
        {
          return this.IsFrozen;
        }
      }

      public bool Remove( FrameworkElement item )
      {
        this.VerifyFrozen();

        return m_innerCollection.Remove( item );
      }

      #endregion

      #region IEnumerable<FrameworkElement> Members

      public IEnumerator<FrameworkElement> GetEnumerator()
      {
        return m_innerCollection.GetEnumerator();
      }

      #endregion

      #region IEnumerable Members

      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
      {
        return ( ( IEnumerable )m_innerCollection ).GetEnumerator();
      }

      #endregion

      #region INotifyCollectionChanged Members

      public event NotifyCollectionChangedEventHandler CollectionChanged;

      #endregion

      private ObservableCollection<FrameworkElement> m_innerCollection;
    }

    #endregion
  }
}
